package org.gitman.agent.gui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.swing.BorderFactory;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.UnsupportedLookAndFeelException;

import org.gitman.agent.exceptions.NoGraphException;
import org.gitman.agent.platform.Agent;
import org.gitman.agent.platform.AgentManager;
import org.gitman.agent.platform.AgentState;
import org.gitman.agent.platform.PlatformManager;
import org.gitman.agent.platform.Simulation;
import org.w3c.dom.Document;

import com.mxgraph.io.mxCodec;
import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxGraphModel;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.util.mxCellRenderer;
import com.mxgraph.util.mxResources;
import com.mxgraph.util.mxXmlUtils;
import com.mxgraph.util.png.mxPngEncodeParam;
import com.mxgraph.util.png.mxPngImageEncoder;
import com.mxgraph.util.png.mxPngTextDecoder;
import com.mxgraph.view.mxGraph;


public class MainFrame extends JFrame implements WindowListener{
	
	private final static String title = "Agent Platform";
	private final static double version = 0.2;
	private final static String credits = "Created by Gitman 2012   ";
	
	private PlatformManager platformManager;
	private String defaultGraphFileName = "./graphs/Graph.png";
	private String fileName = null;
	private String laf;
	private String confFile;
	private GraphPanel graphPanel;
	private SimulationPanel simulationPanel;
	private SummaryPanel summaryPanel;
	private JTabbedPane optionsTabbedPane;
	private GraphDisplPanel graphDisplPanel;
	private JTextPane logConsole;
	
	public MainFrame(String laf, String confFile){
		this.laf = laf;
		this.confFile = confFile;

		initBasic();
		initPlatform();
		createMenu();
		createGUI();
		
	}
	
	public void initBasic(){	
		try{
			UIManager.setLookAndFeel(laf);
		}catch (Exception e) {
			e.printStackTrace();
		}
		addWindowListener(this);
		setTitle(title + " version " + version);
		
	}
	
	private void initPlatform(){
		platformManager = new PlatformManager(this);
	}

	private void createMenu(){
		JMenuBar menuBar = new JMenuBar();
		
		JMenu file = new JMenu("File");
		JMenuItem newGraph = new JMenuItem("New Graph");
		newGraph.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent arg0) {
				platformManager.getGraphEditor().createGraph();
				fileName = null;
				platformManager.newSimulation();
			}
		});
		file.add(newGraph);
		file.addSeparator();
		JMenuItem loadGraph = new JMenuItem("Load Graph");
		loadGraph.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent e) {
				JFileChooser fc = new JFileChooser("./graphs");
				int rc = fc.showOpenDialog(MainFrame.this);
				if(rc == JFileChooser.APPROVE_OPTION){
					File f = fc.getSelectedFile();
					try {
						mxGraph graph = load(f);
						platformManager.getGraphEditor().setGraph(graph);
						fileName = f.getAbsolutePath();
					} catch (IOException ex) {
						JOptionPane.showMessageDialog(MainFrame.this, ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
						ex.printStackTrace();
					}
					
				}
			}
		});
		file.add(loadGraph);		
		JMenuItem saveas = new JMenuItem("Save Graph");
		saveas.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent e) {
				if(platformManager.getGraphEditor().getGraph() == null){
					JOptionPane.showMessageDialog(MainFrame.this, "No graph to be saved", "Error", JOptionPane.ERROR_MESSAGE);
				}else{
					JFileChooser fc = new JFileChooser("./graphs");
					int rc = fc.showSaveDialog(MainFrame.this);
					if(rc == JFileChooser.APPROVE_OPTION){
						File f = fc.getSelectedFile();
						try{
							save(f);
							fileName = f.getAbsolutePath();
						}catch (IOException ex) {
							JOptionPane.showMessageDialog(MainFrame.this, ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
							ex.printStackTrace();
						}
						
					}
				}
			}
		});
		file.add(saveas);
		file.addSeparator();
		
		JMenuItem loadGraphAndSimulation = new JMenuItem("Load Simulation");
		loadGraphAndSimulation.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent arg0) {
				JFileChooser fc = new JFileChooser("./graphs");
				int rc = fc.showOpenDialog(MainFrame.this);
				if(rc == JFileChooser.APPROVE_OPTION){
					File f = fc.getSelectedFile();
					try {
						loadSimulation(f);
					} catch (IOException ex) {
						JOptionPane.showMessageDialog(MainFrame.this, ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
						ex.printStackTrace();
					}
					
				}
			}
		});
		file.add(loadGraphAndSimulation);
		JMenuItem saveGraphAndSimulation = new JMenuItem("Save Simulation");
		saveGraphAndSimulation.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent arg0) {
				if(platformManager.getGraphEditor().getGraph() == null){
					JOptionPane.showMessageDialog(MainFrame.this, "No graph or simulation to be saved", "Error", JOptionPane.ERROR_MESSAGE);
				}else{
					JFileChooser fc = new JFileChooser("./graphs");
					int rc = fc.showSaveDialog(MainFrame.this);
					if(rc == JFileChooser.APPROVE_OPTION){
						File f = fc.getSelectedFile();
						try{
							saveSimulation(f);
						}catch (IOException ex) {
							JOptionPane.showMessageDialog(MainFrame.this, ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
							ex.printStackTrace();
						}
						
					}
				}
			}
		});
		file.add(saveGraphAndSimulation);
		file.addSeparator();
		JMenuItem exit = new JMenuItem("Exit");
		exit.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent arg0) {
				shutDown();			
			}
		});
		file.add(exit);
		menuBar.add(file);
		
		JMenu tools = new JMenu("Graph");
		JMenuItem generateGraph = new JMenuItem("Generate Graph");
		generateGraph.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent arg0) {
				graphPanel.generateGraph();				
			}
		});
		tools.add(generateGraph);
		tools.addSeparator();
		JMenuItem startEdit = new JMenuItem("Start Editing");
		startEdit.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent e) {
				try {
					platformManager.getGraphEditor().startEditing();
				} catch (NoGraphException ex) {
					JOptionPane.showMessageDialog(MainFrame.this, ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
					ex.printStackTrace();
				}	
				
			}
		});
		tools.add(startEdit);
		JMenuItem endEdit = new JMenuItem("Finish Editing");
		endEdit.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent e) {
				platformManager.getGraphEditor().finishEditing();				
			}
		});
		tools.add(endEdit);
		menuBar.add(tools);
		
		JMenu simulation = new JMenu("Simulation");
		JMenuItem newSim = new JMenuItem("New Simulation");
		newSim.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent e) {
				optionsTabbedPane.setSelectedIndex(1);
				platformManager.newSimulation();
			}
		});
		simulation.add(newSim);
		simulation.addSeparator();
		JMenuItem setup = new JMenuItem("Set Up Enviroment");
		setup.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent e) {
				optionsTabbedPane.setSelectedIndex(1);
				JOptionPane.showMessageDialog(MainFrame.this, "Set up simmulation enviroment in Simulation panel\n(open on the left side of the window) ", "Set Up Enviroment", JOptionPane.INFORMATION_MESSAGE);
			}
		});
		simulation.add(setup);
		simulation.addSeparator();
		JMenuItem summary = new JMenuItem("Show Summary");
		summary.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent e) {
				optionsTabbedPane.setSelectedIndex(2);
				JOptionPane.showMessageDialog(MainFrame.this, "Look at the summary in Summary panel\n(open on the left side of the window) ", "Show Summary", JOptionPane.INFORMATION_MESSAGE);
			}
		});
		simulation.add(summary);
		menuBar.add(simulation);
		
		JMenu view = new JMenu("View");
		for(final LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()){
			JMenuItem item = new JMenuItem(info.getName());
			item.addActionListener(new ActionListener() {
				
				@Override
				public void actionPerformed(ActionEvent arg0) {
					try {
						UIManager.setLookAndFeel(info.getClassName());
						SwingUtilities.updateComponentTreeUI(MainFrame.this);
					} catch (ClassNotFoundException | InstantiationException
							| IllegalAccessException
							| UnsupportedLookAndFeelException e) {
						e.printStackTrace();
					}
					
				}
			});
			view.add(item);
		}
		menuBar.add(view);
		
		JMenu help = new JMenu("Help");
		JMenuItem about = new JMenuItem("About " + title);
		about.addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent arg0) {
				AboutDialog aboutDialog = new AboutDialog(MainFrame.this, title, version);		
				aboutDialog.setVisible(true);
			}
		});
		help.add(about);
		menuBar.add(help);
		
		setJMenuBar(menuBar);
	}
	
	private void createGUI(){
		JPanel parent = new JPanel(new BorderLayout());
		add(parent);
		
		optionsTabbedPane = new JTabbedPane();
		optionsTabbedPane.setPreferredSize(new Dimension(300, 100));
		parent.add(optionsTabbedPane, BorderLayout.WEST);
		graphPanel = new GraphPanel(platformManager, this);
		simulationPanel = new SimulationPanel(platformManager, this);
		summaryPanel =  new SummaryPanel(platformManager);
		optionsTabbedPane.addTab("Graph", graphPanel);
		optionsTabbedPane.addTab("Simulation", simulationPanel);
		optionsTabbedPane.addTab("Summary", summaryPanel);
		
		JPanel mainPanel = new JPanel(new BorderLayout());
		graphDisplPanel = new GraphDisplPanel(platformManager);
		mainPanel.add(graphDisplPanel, BorderLayout.CENTER);
		logConsole = new JTextPane();
		logConsole.setEditable(false);
		JScrollPane logPane = new JScrollPane(logConsole);
		logPane.setBorder(BorderFactory.createTitledBorder("Logging Console"));
		logPane.setPreferredSize(new Dimension(100, 90));
		mainPanel.add(logPane, BorderLayout.SOUTH);
		parent.add(mainPanel, BorderLayout.CENTER);
		
		JPanel bottom = new JPanel(new BorderLayout());		
		bottom.add(new JLabel(credits), BorderLayout.EAST);
		parent.add(bottom, BorderLayout.SOUTH);
		
	}
	
	private void save(File f) throws IOException{
		if(!f.exists() && !f.getName().endsWith(".png")){
			f = new File(f.getAbsoluteFile() + ".png");
		}
		mxGraph graph = platformManager.getGraphEditor().getGraph();

		// Creates the image for the PNG file
		BufferedImage image = mxCellRenderer.createBufferedImage(graph,
				null, 1.0, Color.white, true, null, graphDisplPanel.getGraphComponent().getCanvas());

		// Creates the URL-encoded XML data
		mxCodec codec = new mxCodec();
		String xml = URLEncoder.encode(mxXmlUtils.getXml(codec.encode(graph.getModel())), "UTF-8");
		mxPngEncodeParam param = mxPngEncodeParam.getDefaultEncodeParam(image);
		param.setCompressedText(new String[] { "mxGraphModel", xml });

		// Saves as a PNG file
		FileOutputStream outputStream = new FileOutputStream(f);
		try{
			mxPngImageEncoder encoder = new mxPngImageEncoder(outputStream, param);

			if (image != null){
				encoder.encode(image);

			}
		}
		finally
		{
			outputStream.close();
		}
	}
	
	private mxGraph load(File f) throws IOException{
		mxGraph graph = null;
		Map<String, String> text = mxPngTextDecoder.decodeCompressedText(new FileInputStream(f));
		if (text != null){
			String value = text.get("mxGraphModel");

			if (value != null){
				Document document = mxXmlUtils.parseXml(URLDecoder.decode(value, "UTF-8"));
				mxCodec codec = new mxCodec(document);
				graph = new mxGraph();
				codec.decode(document.getDocumentElement(), graph.getModel());
			}
		}
		platformManager.getGraphEditor().setGraph(graph);
		return graph;
	}
	
	private void shutDown(){
		try {
			PrintWriter out = new PrintWriter(confFile);
			Properties properties = new Properties();
			properties.setProperty("mainFrame.width", getWidth() + "");
			properties.setProperty("mainFrame.height", getHeight() + "");
			properties.setProperty("mainFrame.laf", UIManager.getLookAndFeel().getClass().getName());
			properties.store(out, "");
		} catch (IOException e) {
			e.printStackTrace();
		}		
		System.exit(1);
	}

	public GraphPanel getGraphPanel() {
		return graphPanel;
	}

	public SimulationPanel getSimulationPanel() {
		return simulationPanel;
	}

	public SummaryPanel getSummaryPanel() {
		return summaryPanel;
	}

	public GraphDisplPanel getGraphDisplPanel() {
		return graphDisplPanel;
	}
	
	public void appendLog(String message){
		String text = logConsole.getText();
		String fullText = null;
		if(text == null || text.equals("")){
			fullText = message;
		}else{
			fullText = logConsole.getText() + "\n" + message;
		}
		logConsole.setText(fullText);
		logConsole.setCaretPosition(fullText.length());
	}
	
	private void saveSimulation(File f) throws IOException{
		save(new File(f.getAbsolutePath() + ".png"));
		PrintWriter out = new PrintWriter(f);
		List<Agent> agents = platformManager.getSimulation().getAgentManager().getAgents();
		try{
			out.println(agents.size());
			for(Agent agent : agents){
				out.println(agent.getClass().getCanonicalName());
				out.println(agent.getColor().getRGB());
				out.println(agent.getStartPoint().getId());
				out.println(agent.getEndPoint().getId());
				out.println(agent.getState());
			}
		}finally{
			out.close();
		}		
	}
		
	private void loadSimulation(File f) throws IOException{
		load(new File(f.getAbsoluteFile() + ".png"));
		platformManager.newSimulation();
		mxGraph graph = platformManager.getGraphEditor().getGraph();
		mxGraphModel model = ((mxGraphModel)graph.getModel());
		Simulation simulation = platformManager.getSimulation();
		AgentManager manager = simulation.getAgentManager();
		BufferedReader in = new BufferedReader(new FileReader(f));
		ClassLoader loader = MainFrame.class.getClassLoader();
		try{
			int size = Integer.parseInt(in.readLine());
			for(int i =0;i<size;i++){
				String name = in.readLine();
				Agent fabric = (Agent) loader.loadClass(name).newInstance();
				Agent agent = fabric.getInstance();
				agent.setColor(new Color(Integer.parseInt(in.readLine())));
				agent.setStartPoint((mxCell) model.getCell(in.readLine()));
				agent.setEndPoint((mxCell) model.getCell(in.readLine()));			
				agent.setState(AgentState.valueOf(in.readLine()));
				agent.setSimulation(simulation);
				manager.addAgent(agent);
			}
		} catch (InstantiationException | IllegalAccessException | ClassNotFoundException  e) {
			e.printStackTrace();
		}finally{
			in.close();
		}
		simulationPanel.updateAgentsTab();		
	}
	
	@Override
	public void windowClosing(WindowEvent arg0) {
		shutDown();
	}
	
	@Override
	public void windowActivated(WindowEvent arg0) {}
	
	@Override
	public void windowClosed(WindowEvent arg0) {}
		
	@Override
	public void windowDeactivated(WindowEvent arg0) {}
	
	@Override
	public void windowDeiconified(WindowEvent arg0) {}
	
	@Override
	public void windowIconified(WindowEvent arg0) {}
	
	@Override
	public void windowOpened(WindowEvent arg0) {}
	
	
}
